package com.loe.ktui

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.TypedArray
import android.graphics.*
import android.graphics.drawable.Drawable
import android.text.TextUtils.TruncateAt
import android.util.AttributeSet
import android.util.TypedValue
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.res.*
import androidx.core.text.HtmlCompat
import androidx.core.view.isVisible
import com.loe.ktui.util.KtColor
import com.loe.ktui.util.KtCornerProvider
import com.loe.ktui.util.UiShape
import kotlin.properties.ObservableProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

open class UiText(id: String? = null) : UiView(id)
{
    companion object
    {
        const val TEXT_BOLD = "bold"
        const val TEXT_DELETE = "delete"
        const val TEXT_UNDER = "under"
        const val TEXT_NORMAL = "normal"
    }

    open var text: CharSequence? = null
    open var textColor: Int? = null
    open var autoText: CharSequence? = null
    open var hint: String? = null
    open var hintColor: Int? = null
    open var gravity: Int? = null
    open var isTextSelectable: Boolean? = null
    open var minLines: Int? = null
    open var maxLines: Int? = null
    open var maxEms: Int? = null
    open var ellipsis: TruncateAt? = null
    open var bold: Boolean? = null
    open var italic: Boolean? = null
    open var textStyle: String? = null
    open var fontWeight: Double? = null
    open var textSize: Any? = null
    open var letterSpacing: Double? = null
    open var isAutoVisible: Boolean? = null
    open var image: Any? = null
    open var imageRes: Int? = null
    open var imageTop: Any? = null
    open var imageWidth: Any? = null
    open var imageHeight: Any? = null
    open var imagePadding: Any? = null
    open var imageColor: Int? = null
    open var imageDirect: String? = null

    open var bindText: String? = null
    open var bindHtml: String? = null
    open var labelTitleColor: Int? = null
    open var labelTailColor: Int? = null
    open var labelTitle: String? = null
    open var labelText: String? = null
    open var labelTail: String? = null

    open var isAlphaTextClick: Boolean? = null

    open var onText: (KtTextView.(s: String) -> Unit)? = null
    open fun onText(onText: KtTextView.(s: String) -> Unit)
    {
        this.onText = onText
    }

    open fun initUI(context: Context, view: KtTextView)
    {
        super.initUI(context, view)

        isAlphaClick?.let { view.isAlphaClick = it }
        isAlphaTextClick?.let { view.isAlphaTextClick = it }

        isAutoVisible?.let { view.isAutoVisible = it }
        text?.let { view.text = it }
        autoText?.let { view.autoText = it }
        view.setTextColor(textColor ?: KtColor.text)
        hint?.let { view.hint = it }
        hintColor?.let { view.setHintTextColor(it) }
        gravity?.let { view.gravity = it }
        isTextSelectable?.let { view.setTextIsSelectable(it) }
        minLines?.let { view.minLines = it }
        maxLines?.let { view.maxLines = it }
        maxEms?.let { view.maxEms = it }
        ellipsis?.let { view.ellipsize = it }
        bold?.let { view.setBold(it) }
        italic?.let { view.setItalic(it) }
        textStyle?.let { view.setTextStyle(it) }
        fontWeight?.let { view.fontWeight = it }
        textSize?.let { view.setTextSize(TypedValue.COMPLEX_UNIT_PX, it.distFloat) }
        letterSpacing?.let { view.letterSpacing = it.toFloat() }

        bindText?.let { view.bindText = it }
        bindHtml?.let { view.bindHtml = it }
        view.isLabelLock = true
        labelTitleColor?.let { view.labelTitleColor = it }
        labelTailColor?.let { view.labelTailColor = it }
        labelTitle?.let { view.labelTitle = it }
        labelTail?.let { view.labelTail = it }
        labelText?.let { view.labelText = it }
        view.isLabelLock = false
        if (labelTitle != null || labelText != null || labelTail != null) view.updateLabel()

        fun initImage()
        {
            view.imageTop = imageTop.dist
            view.imageWidth = (imageWidth ?: 14).dist
            view.imageHeight = (imageHeight ?: 14).dist
            view.imagePadding = (imagePadding ?: 4).dist
            view.imageColor = imageColor
            view.imageDirect = imageDirect ?: ""
            view.notifyImage()
        }
        image?.let()
        {
            view.image = it
            initImage()
        }
        imageRes?.let()
        {
            view.imageRes = it
            initImage()
        }

        onText?.let { onText -> view.setTextListener { view.onText(it) } }
    }

    /** 创建view */
    override fun createView(context: Context, parent: ViewGroup?): KtTextView
    {
        return KtTextView(context).also()
        {
            initUI(context, it)
            addToParent(parent, it)
        }
    }
}

fun Any.ui_Text(id: String? = null, block: (UiText.() -> Unit)? = null): UiText
{
    val ui = UiText(id)
    block?.let { ui.it() }
    if (this is UiGroup) addChild(ui)
    return ui
}

@SuppressLint("AppCompatCustomView")
open class KtTextView : TextView
{
    constructor(context: Context) : super(context)
    {
        initXML()
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
    {
        initXML(context.obtainStyledAttributes(attrs, R.styleable.KtTextView))
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
    {
        initXML(context.obtainStyledAttributes(attrs, R.styleable.KtTextView, defStyleAttr, 0))
    }

    private fun initXML(typedArray: TypedArray? = null)
    {
        // xml参数
        typedArray?.let()
        {
            if (it.hasValue(R.styleable.KtTextView_kt_corner))
            {
                val corner = it.getDimensionOrThrow(R.styleable.KtTextView_kt_corner).toInt()
                val cornerDirect = KtDirects[it.getInt(R.styleable.KtTextView_kt_cornerDirect, 4)]

                outlineProvider = KtCornerProvider(corner, cornerDirect, this)
            }
            if (it.hasValue(R.styleable.KtTextView_kt_isAlphaClick))
            {
                isAlphaClick = it.getBooleanOrThrow(R.styleable.KtTextView_kt_isAlphaClick)
            }
            if (it.hasValue(R.styleable.KtTextView_kt_isAlphaTextClick))
            {
                isAlphaTextClick = it.getBooleanOrThrow(R.styleable.KtTextView_kt_isAlphaTextClick)
                setTextColor(currentTextColor)
            }
            if (it.hasValue(R.styleable.KtTextView_kt_link))
            {
                link = it.getStringOrThrow(R.styleable.KtTextView_kt_link)
            }

            if (it.hasValue(R.styleable.KtTextView_kt_textStyle))
            {
                when (it.getInt(R.styleable.KtTextView_kt_textStyle, 0))
                {
                    1 -> paint.flags = Paint.FAKE_BOLD_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG
                    2 -> paint.flags = Paint.STRIKE_THRU_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG
                    3 -> paint.flags = Paint.UNDERLINE_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG
                    // normal
                    else -> paint.flags = Paint.ANTI_ALIAS_FLAG
                }
            }
            if (it.hasValue(R.styleable.KtTextView_kt_fontWeight))
            {
                fontWeight = it.getFloatOrThrow(R.styleable.KtTextView_kt_fontWeight).toDouble()
            }
            if (it.hasValue(R.styleable.KtTextView_kt_isAutoVisible))
            {
                isAutoVisible = it.getBooleanOrThrow(R.styleable.KtTextView_kt_isAutoVisible)
            }
            if (it.hasValue(R.styleable.KtTextView_kt_autoText))
            {
                autoText = it.getStringOrThrow(R.styleable.KtTextView_kt_autoText)
            }

            if (it.hasValue(R.styleable.KtTextView_kt_bindText))
            {
                bindText = it.getStringOrThrow(R.styleable.KtTextView_kt_bindText)
            }
            if (it.hasValue(R.styleable.KtTextView_kt_bindHtml))
            {
                val html = it.getStringOrThrow(R.styleable.KtTextView_kt_bindHtml)
                bindHtml = html.replace("[", "<")
                    .replace("]", ">")
                    .replace("□", "&nbsp;")
            }

            isLabelLock = true
            if (it.hasValue(R.styleable.KtTextView_kt_labelTitleColor))
            {
                labelTitleColor = it.getColorOrThrow(R.styleable.KtTextView_kt_labelTitleColor)
            }
            if (it.hasValue(R.styleable.KtTextView_kt_labelTailColor))
            {
                labelTailColor = it.getColorOrThrow(R.styleable.KtTextView_kt_labelTailColor)
            }
            if (it.hasValue(R.styleable.KtTextView_kt_labelTitle))
            {
                labelTitle = it.getStringOrThrow(R.styleable.KtTextView_kt_labelTitle)
            }
            if (it.hasValue(R.styleable.KtTextView_kt_labelText))
            {
                labelText = it.getStringOrThrow(R.styleable.KtTextView_kt_labelText)
            }
            if (it.hasValue(R.styleable.KtTextView_kt_labelTail))
            {
                labelTail = it.getStringOrThrow(R.styleable.KtTextView_kt_labelTail)
            }
            isLabelLock = false
            if ((labelTitle + labelText + labelTail).isNotEmpty()) updateLabel()

            fun initImage()
            {
                imageTop = it.getDimensionPixelOffset(R.styleable.KtTextView_kt_imageTop, 0)
                imageWidth = it.getDimensionPixelOffset(R.styleable.KtTextView_kt_imageWidth, ktPx(14.0))
                imageHeight = it.getDimensionPixelOffset(R.styleable.KtTextView_kt_imageHeight, ktPx(14.0))
                imagePadding = it.getDimensionPixelOffset(R.styleable.KtTextView_kt_imagePadding, ktPx(4.0))
                ktSafe { imageColor = it.getColorOrThrow(R.styleable.KtTextView_kt_imageColor) }
                imageDirect = KtDirects[it.getInt(R.styleable.KtTextView_kt_imageDirect, 4)]
                notifyImage()
            }
            if (it.hasValue(R.styleable.KtTextView_kt_image))
            {
                imageRes = it.getResourceIdOrThrow(R.styleable.KtTextView_kt_image)
                initImage()
            }

            it.recycle()
        }
    }

    var code = ""

    /******************************************* 图片 *******************************************/

    var image: Any? = null
    var imageRes: Int? = null

    var imageTop = 0
    var imageWidth = 0
    var imageHeight = 0
    var imagePadding = 0
    var imageColor: Int? = null
    var imageDirect = ""

    fun notifyImage()
    {
        if (image is UiShape)
        {
            setImage((image as UiShape).createDrawable())
        } else if (imageRes != null)
        {
            setImage(resources.getDrawable(imageRes!!, context.theme))
        } else
        {
            context.getUrlDrawable(image.toString()) { setImage(it) }
        }
    }

    fun setImage(it: Drawable)
    {
        var drawable = it
        drawable.setBounds(0, imageTop, imageWidth, imageHeight + imageTop)
        if (imageColor != null)
        {
            drawable = drawable.mutate()
            drawable.colorFilter = PorterDuffColorFilter(imageColor!!, PorterDuff.Mode.SRC_ATOP)
        }
        val ds = compoundDrawables
        when (imageDirect)
        {
            "top" -> ds[1] = drawable
            "right" -> ds[2] = drawable
            "bottom" -> ds[3] = drawable
            else -> ds[0] = drawable
        }
        setCompoundDrawables(ds[0], ds[1], ds[2], ds[3])
        compoundDrawablePadding = imagePadding
    }

    /******************************************* 其他 *******************************************/

    var link = ""
        set(value)
        {
            field = value
            setKtDelayClickListener { KtUi.onLink?.invoke(context, value) }
        }

    var isAutoVisible: Boolean = false

    var autoText: CharSequence?
        get() = text
        set(value)
        {
            isAutoVisible = true
            isVisible = value != null && value.trim().isNotEmpty()
            text = value
        }

    var isAlphaClick = false
        set(value)
        {
            field = value
            isClickable = value
        }

    override fun dispatchSetPressed(pressed: Boolean)
    {
        if (isAlphaClick) alpha = if (pressed || !isEnabled) 0.75f else 1f
        super.dispatchSetPressed(pressed)
    }

    override fun setEnabled(enabled: Boolean)
    {
        if (isAlphaClick) alpha = if (enabled) 1f else 0.75f
        super.setEnabled(enabled)
    }

    var isAlphaTextClick = false
        set(value)
        {
            field = value
            isClickable = value
        }

    override fun setTextColor(color: Int)
    {
        if (isAlphaTextClick)
        {
            val states = arrayOfNulls<IntArray>(3)
            states[0] = intArrayOf(android.R.attr.state_enabled, -android.R.attr.state_pressed)
            states[1] = intArrayOf(android.R.attr.state_enabled, android.R.attr.state_pressed)
            states[2] = intArrayOf(-android.R.attr.state_enabled)
            val alphaColor = (0xaa shl 24) + (color and 0xFFFFFF)
            setTextColor(ColorStateList(states, intArrayOf(color, alphaColor, alphaColor)))
        } else
        {
            super.setTextColor(color)
        }
    }

    private var onText: ((s: String) -> Unit)? = null

    private val addTextChanged by lazy {
        addKtTextAfterListener()
        {
            onText?.invoke(it)
        }
    }

    fun setTextListener(initText: String? = null, onText: (s: String) -> Unit)
    {
        if (initText != null)
        {
            this.onText = null
            text = initText
        }
        this.onText = onText
        addTextChanged
    }

    var fontWeight: Double = 1.0
        set(value)
        {
            field = value
            if (value > 1)
            {
                paint.strokeWidth = (value - 1).toFloat()
                paint.style = Paint.Style.FILL_AND_STROKE
            } else
            {
                paint.strokeWidth = 0f
                paint.style = Paint.Style.FILL
            }
            invalidate()
        }

    /******************************************* bind *******************************************/

    private val bindMap = HashMap<String, String>()
    var bindText: String = ""
        set(value)
        {
            field = value
            bindMap.clear()

            var start = -1
            value.forEachIndexed()
            { i, c ->
                if (c == '{') start = i
                else if (c == '}' && start > 0)
                {
                    val key = value.substring(start, i + 1)
                    bindMap[key] = ""
                    start = -1
                }
            }

            updateBind()
        }

    var bindHtml: String = ""
        set(value)
        {
            field = value.replace("\n", "<br/>")
            bindMap.clear()

            var start = -1
            field.forEachIndexed()
            { i, c ->
                if (c == '{') start = i
                else if (c == '}' && start > 0)
                {
                    val key = field.substring(start, i + 1)
                    bindMap[key] = ""
                    start = -1
                }
            }

            updateBind()
        }

    private fun updateBind()
    {
        if (bindText.isNotEmpty())
        {
            var newText = bindText
            bindMap.forEach()
            {
                newText = newText.replace(it.key, it.value)
            }
            text = newText
        } else
        {
            var newText = bindHtml
            bindMap.forEach()
            {
                newText = newText.replace(it.key, it.value)
            }
            text = HtmlCompat.fromHtml(newText, HtmlCompat.FROM_HTML_MODE_LEGACY)
        }
    }

    operator fun set(k: String, v: String)
    {
        bindMap["{$k}"] = v
        updateBind()
    }

    operator fun get(k: String): String
    {
        return bindMap[k].toString()
    }

    fun bind(key: String): ReadWriteProperty<Any?, String> = object : ObservableProperty<String>("")
    {
        val rKey = "{$key}"
        override fun afterChange(property: KProperty<*>, oldValue: String, newValue: String)
        {
            bindMap[rKey] = newValue
            updateBind()
        }
    }

    /******************************************* label *******************************************/

    private var titleColor: String = ""
    private var tailColor: String = ""

    var labelTitleColor: Int = 0
        set(value)
        {
            field = value
            titleColor = String.format("#%06X", 0xFFFFFF and value)
            updateLabel()
        }

    var labelTailColor: Int = 0
        set(value)
        {
            field = value
            tailColor = String.format("#%06X", 0xFFFFFF and value)
            updateLabel()
        }

    var labelTitle: String = ""
        set(value)
        {
            field = value
            updateLabel()
        }

    var labelTail: String = ""
        set(value)
        {
            field = value
            updateLabel()
        }

    var labelText: String = ""
        set(value)
        {
            field = value
            updateLabel()
        }

    internal var isLabelLock = false

    fun updateLabel()
    {
        if (isLabelLock) return
        if (isAutoVisible) isVisible = labelText.isNotEmpty()
        text = (labelTitle.ktC(titleColor) + labelText + labelTail.ktC(tailColor)).ktHtml
    }

    /***************************************** 文本样式 ***************************************/

    fun setBold(bold: Boolean)
    {
        typeface = if (bold) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
    }

    fun setItalic(italic: Boolean)
    {
        setTypeface(Typeface.DEFAULT, if (italic) Typeface.ITALIC else Typeface.NORMAL)
    }

    fun setTextStyle(textStyle: String)
    {
        when (textStyle)
        {
            "bold" -> paint.flags = Paint.FAKE_BOLD_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG
            "delete" -> paint.flags = Paint.STRIKE_THRU_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG
            "under" -> paint.flags = Paint.UNDERLINE_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG
            // normal
            else -> paint.flags = Paint.ANTI_ALIAS_FLAG
        }
    }
}